Jerry's Log

Builder in JAVA

contents

롬복(Lombok) 라이브러리에서 개발자들이 사랑하는 애노테이션 중 하나인 @Builder 에 대해 알아보겠습니다.

이 애노테이션은 빌더 패턴(Builder Pattern) 을 자동으로 구현해 줍니다.

빌더 패턴은 복잡한 객체를 단계별로 생성할 수 있게 해주는 "생성 디자인 패턴"입니다. 롬복 없이 빌더를 직접 짜려면 지루한 보일러플레이트 코드(상용구)를 50줄 넘게 작성해야 하지만, @Builder를 쓰면 단 한 줄로 끝납니다.

작동 원리와 내부 구조, 그리고 흔히 겪는 실수들에 대한 분석입니다.


1. 문제점: "점층적 생성자 (Telescoping Constructor)"

필드가 5개인 User 클래스가 있다고 상상해 봅시다.

// 빌더 없이 생성할 때
User user = new User("John", "Doe", 30, "john@email.com", true);

빌더 사용 시:

User user = User.builder()
    .firstName("John")
    .lastName("Doe")
    .age(30)
    .email("john@email.com")
    .active(true)
    .build();

2. 사용 방법

클래스 위에 애노테이션만 붙이면 됩니다.

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class User {
    private final String firstName;
    private final String lastName;
    private int age;
}

3. 내부 동작: 롬복이 생성하는 코드

이 코드를 컴파일하면, 롬복의 애노테이션 프로세서가 정적 내부 클래스(Static Inner Class) 를 생성해 줍니다. 대략 아래와 같은 모양입니다.

public class User {
    private final String firstName;
    private final String lastName;
    private int age;

    // 1. private 생성자 (외부에서 직접 생성 못 하고 빌더를 쓰도록 강제)
    User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    // 2. 빌더 인스턴스를 얻는 정적 메서드
    public static UserBuilder builder() {
        return new UserBuilder();
    }

    // 3. 정적 내부 클래스 (실제 빌더)
    public static class UserBuilder {
        private String firstName;
        private String lastName;
        private int age;

        UserBuilder() {}

        // 4. 체이닝이 가능한 세터(Setter) 메서드들
        public UserBuilder firstName(String firstName) {
            this.firstName = firstName;
            return this; // 'this'를 반환하여 메서드 체이닝 가능
        }

        public UserBuilder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        // 5. 최종 객체 생성 메서드
        public User build() {
            return new User(firstName, lastName, age);
        }
    }
}

4. 고급 기능 및 함정 (Gotchas)

A. @Builder.Default (조용한 살인마)

필드에 기본값을 넣어두더라도, @Builder는 기본적으로 이를 무시합니다.

시나리오:

@Builder
public class User {
    private String name;
    private boolean active = true; // 기본값을 true로 설정함
}

사용:

User u = User.builder().name("John").build();
System.out.println(u.isActive()); // false가 출력됩니다! 😱
@Builder.Default
private boolean active = true;

B. @Singular (컬렉션 처리)

ListMap이 있는 경우, 보통은 리스트를 먼저 만들어서 통째로 넘겨줘야 합니다.

@Singular를 쓰면 롬복이 원소를 하나씩 추가하는 메서드를 만들어줍니다.

@Builder
public class Group {
    @Singular
    private List members;
}

// 사용법
Group g = Group.builder()
    .member("John") // 주의: 필드명은 members(복수)지만 메서드는 member(단수)로 생성됨
    .member("Jane")
    .build();

C. toBuilder = true (객체 복사)

이미 존재하는 객체와 거의 똑같은데 필드 몇 개만 바꾼 복사본을 만들고 싶을 때 유용합니다(불변 객체에 특히 유용).

@Builder(toBuilder = true)
public class User { ... }

// 사용법
User user1 = User.builder().name("John").age(30).build();

// user1의 데이터를 그대로 가져오되, 나이만 31로 변경하여 user2 생성
User user2 = user1.toBuilder().age(31).build();

5. 중요 충돌: @Builder + @NoArgsConstructor

개발자들이 가장 많이 겪는 컴파일 에러입니다.

충돌 원인:

  1. @Builder가 작동하려면 모든 인자가 있는 생성자(All-Args Constructor) 가 필요합니다. 생성자가 없으면 롬복이 알아서 만들어 씁니다.
  2. JPA(Hibernate) 엔티티는 기본 생성자(No-Args Constructor) 가 필수입니다.
  3. 그래서 @NoArgsConstructor를 추가합니다.
  4. 자바 규칙상 "개발자가 생성자를 하나라도 직접(여기선 애노테이션으로) 만들면, 기본 생성자는 사라진다"는 원칙 때문에, 1번에서 롬복이 암묵적으로 쓰던 All-Args 생성자가 사라집니다.
  5. 결과: @Builder가 깨집니다.

해결책:

반드시 두 생성자를 모두 명시적으로 추가해야 합니다.

@Entity
@Builder
@NoArgsConstructor // JPA를 위해 필요
@AllArgsConstructor // 빌더를 위해 필요
public class User {
    @Id
    private Long id;
    private String name;
}

6. 요약 테이블

기능 설명
기본 사용 클래스에 @Builder. ClassName.builder()...build() 사용 가능.
기본값 필드 초기값은 무시됨. @Builder.Default를 붙여야 적용됨.
컬렉션 @Singular를 붙이면 리스트에 addOne() 하는 메서드가 생성됨.
복사 toBuilder = true를 쓰면 인스턴스를 복제하고 필드를 수정 가능.
생성자 충돌 JPA와 함께 쓸 때는 반드시 @AllArgsConstructor, @NoArgsConstructor 둘 다 추가.
상속 extends 상속 관계에서는 잘 작동 안 함. @SuperBuilder(실험적 기능) 사용 필요.

references